/************************************************************
* @file   gkt_retmem.c
************************************************************/
#include <string.h>
#include "gkt_board.h"
#include "gkt_debug.h"
#include "gkt_crc.h"

#if GKT_RETMEM_SUPPORT && !GKT_CONFIG_RETMEM_DISABLE

#if defined(GKT_CONFIG_RETMEM_CHECK_VALIDITY_ENABLE) \
	&& GKT_CONFIG_RETMEM_CHECK_VALIDITY_ENABLE
#include "gkt_crc.h"

#define RETMEM_CHECK_MAGIC	0x23CC
#define __RETMEM_CHK	__SECTION(.retmem.chk)

struct _retmem_check {
	uint16_t	magic;
	uint16_t	crc16;
} __ALIGNED(4);
typedef struct _retmem_check retmem_check_s;
static __RETMEM_CHK retmem_check_s sr_retmem_check;

static void retmem_check_validity(void)
{
	const uint8_t *addr;
	uint32_t size;
	uint16_t crc16;

	if (GKT_SYSTEM_RESET_WAKEUP == g_system_info.reset_mode) {
		if (RETMEM_CHECK_MAGIC == sr_retmem_check.magic) {
			addr = (const uint8_t *)GKT_RETMEM_BASEADDR + sizeof(retmem_check_s);
			size = GKT_RETMEM_SIZE - sizeof(retmem_check_s);
			crc16 = gkt_crc16_ccitt_false(addr, size);
			if (crc16 == sr_retmem_check.crc16)
				return;
		}

		gkt_system_reset();
	}
}
static void retmem_check_calculate(void)
{
	const uint8_t *addr;
	uint32_t size;

	addr = (const uint8_t *)GKT_RETMEM_BASEADDR + sizeof(retmem_check_s);
	size = GKT_RETMEM_SIZE - sizeof(retmem_check_s);
	sr_retmem_check.crc16 = gkt_crc16_ccitt_false(addr, size);
	sr_retmem_check.magic = RETMEM_CHECK_MAGIC;
}
#else
#define retmem_check_validity()		do {}  while (0)
#define retmem_check_calculate()	do {}  while (0)
#endif

#define RETMEM_CTRL_LP_CEN	(1U << 0)
#define RETMEM_CTRL_LP_RET	(1U << 1)
#define RETMEM_CTRL_LP_SLP	(1U << 2)

static int s_retmem_reset = 0;

void gkt_retmem_init(void)
{
	uint32_t reg_value;

	/* exit low-power mode */
	reg_value = gkt_read_reg32(GKT_RETMEM_CTRL_BASEADDR + GKT_RETMEM_CTRL_LP);
	if (reg_value & RETMEM_CTRL_LP_CEN) {
		reg_value &= ~(RETMEM_CTRL_LP_RET | RETMEM_CTRL_LP_SLP);
		gkt_write_reg32(GKT_RETMEM_CTRL_BASEADDR + GKT_RETMEM_CTRL_LP, reg_value);

		reg_value &= ~RETMEM_CTRL_LP_CEN;
		gkt_write_reg32(GKT_RETMEM_CTRL_BASEADDR + GKT_RETMEM_CTRL_LP, reg_value);
	}

	retmem_check_validity();

	if (g_system_info.reset_mode != GKT_SYSTEM_RESET_WAKEUP) {
		s_retmem_reset = 1;
		memset((void *)GKT_RETMEM_BASEADDR, 0, GKT_RETMEM_SIZE);
	}
}

static const uint32_t sc_retmem_lp_modes[GKT_RETMEM_LP_MAX - 1] __SYSINFO_RODATA = {
	RETMEM_CTRL_LP_RET,
	RETMEM_CTRL_LP_SLP
};
void gkt_retmem_lp(uint32_t mode)
{
	uint32_t reg_value;

	retmem_check_calculate();

	if ((mode > GKT_RETMEM_LP_NONE) 
		&& (mode < GKT_RETMEM_LP_MAX)) 
	{
		reg_value = RETMEM_CTRL_LP_CEN;
		gkt_write_reg32(GKT_RETMEM_CTRL_BASEADDR + GKT_RETMEM_CTRL_LP, reg_value);

		reg_value |= sc_retmem_lp_modes[mode - 1];
		gkt_write_reg32(GKT_RETMEM_CTRL_BASEADDR + GKT_RETMEM_CTRL_LP, reg_value);
	}
	else 
		gkt_write_reg32(GKT_RETMEM_CTRL_BASEADDR + GKT_RETMEM_CTRL_LP, 0);
}

int gkt_retmem_is_reset(void)
{
	return s_retmem_reset;
}
#endif

